feat: landing page first draft

This commit is contained in:
EDRipper 2025-12-06 01:13:42 -05:00
parent 522f1c3391
commit 0292b95247
9 changed files with 1497 additions and 3 deletions

17
app/package.json Normal file
View file

@ -0,0 +1,17 @@
{
"name": "stickers-app",
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^4.0.0",
"@sveltejs/kit": "^2.21.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"svelte": "^5.0.0",
"vite": "^6.0.0"
}
}

10
app/svelte.config.js Normal file
View file

@ -0,0 +1,10 @@
import adapter from '@sveltejs/adapter-auto';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter()
}
};
export default config;

6
app/vite.config.js Normal file
View file

@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});

70
package-lock.json generated
View file

@ -7,6 +7,9 @@
"": {
"name": "stickers",
"version": "0.0.1",
"dependencies": {
"flubber": "^0.4.2"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/kit": "^2.48.5",
@ -1002,6 +1005,12 @@
"node": ">=6"
}
},
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
@ -1012,6 +1021,18 @@
"node": ">= 0.6"
}
},
"node_modules/d3-array": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
"integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==",
"license": "BSD-3-Clause"
},
"node_modules/d3-polygon": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz",
"integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==",
"license": "BSD-3-Clause"
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@ -1047,6 +1068,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/earcut": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz",
"integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==",
"license": "ISC"
},
"node_modules/esbuild": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
@ -1124,6 +1151,20 @@
}
}
},
"node_modules/flubber": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/flubber/-/flubber-0.4.2.tgz",
"integrity": "sha512-79RkJe3rA4nvRCVc2uXjj7U/BAUq84TS3KHn6c0Hr9K64vhj83ZNLUziNx4pJoBumSPhOl5VjH+Z0uhi+eE8Uw==",
"license": "MIT",
"dependencies": {
"d3-array": "^1.2.0",
"d3-polygon": "^1.0.3",
"earcut": "^2.1.1",
"svg-path-properties": "^0.2.1",
"svgpath": "^2.2.1",
"topojson-client": "^3.0.0"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@ -1423,6 +1464,21 @@
"typescript": ">=5.0.0"
}
},
"node_modules/svg-path-properties": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/svg-path-properties/-/svg-path-properties-0.2.2.tgz",
"integrity": "sha512-GmrB+b6woz6CCdQe6w1GHs/1lt25l7SR5hmhF8jRdarpv/OgjLyuQygLu1makJapixeb1aQhP/Oa1iKi93o/aQ==",
"license": "ISC"
},
"node_modules/svgpath": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/svgpath/-/svgpath-2.6.0.tgz",
"integrity": "sha512-OIWR6bKzXvdXYyO4DK/UWa1VA1JeKq8E+0ug2DG98Y/vOmMpfZNj+TIG988HjfYSqtcy/hFOtZq/n/j5GSESNg==",
"license": "MIT",
"funding": {
"url": "https://github.com/fontello/svg2ttf?sponsor=1"
}
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@ -1440,6 +1496,20 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/topojson-client": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz",
"integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==",
"license": "ISC",
"dependencies": {
"commander": "2"
},
"bin": {
"topo2geo": "bin/topo2geo",
"topomerge": "bin/topomerge",
"topoquantize": "bin/topoquantize"
}
},
"node_modules/totalist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",

View file

@ -19,5 +19,8 @@
"svelte-check": "^4.3.4",
"typescript": "^5.9.3",
"vite": "^7.2.2"
},
"dependencies": {
"flubber": "^0.4.2"
}
}

Binary file not shown.

View file

@ -0,0 +1,290 @@
<script>
import { onMount } from 'svelte';
let {
corner = 'bottom-right',
peelOnHover = true,
hoverPeelAmount = 0.15,
peelOnClick = false,
peelAwayDuration = 500,
dragEnabled = false,
borderRadius = '0',
class: className = '',
topContent,
backContent,
bottomContent,
onPeel = () => {},
onPeelComplete = () => {},
options = {}
} = $props();
let container = $state(null);
let peel = $state(null);
let isPeeled = $state(false);
let isHovering = $state(false);
let width = $state(0);
let height = $state(0);
let cornerValue = $state(3);
let currentPos = $state(null);
let isActive = $state(false);
let animationId = $state(0);
const cornerMap = {
'top-left': 0,
'top-right': 1,
'bottom-left': 2,
'bottom-right': 3
};
function getRestPosition() {
switch (cornerValue) {
case 0: return { x: 0, y: 0 };
case 1: return { x: width, y: 0 };
case 2: return { x: 0, y: height };
case 3: return { x: width, y: height };
}
}
function getHoverPosition() {
const offset = Math.min(width, height) * hoverPeelAmount;
switch (cornerValue) {
case 0: return { x: offset, y: offset };
case 1: return { x: width - offset, y: offset };
case 2: return { x: offset, y: height - offset };
case 3: return { x: width - offset, y: height - offset };
}
}
function getPeeledPosition() {
switch (cornerValue) {
case 0: return { x: width * 1.5, y: height * 1.5 };
case 1: return { x: -width * 0.5, y: height * 1.5 };
case 2: return { x: width * 1.5, y: -height * 0.5 };
case 3: return { x: -width * 0.5, y: -height * 0.5 };
}
}
function animateTo(targetX, targetY, duration, callback, showLayers = true) {
if (!peel) return;
animationId++;
const thisAnimationId = animationId;
const startTime = performance.now();
const startPos = currentPos || getRestPosition();
let firstFrame = true;
function animate(currentTime) {
if (thisAnimationId !== animationId) return;
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3);
const x = startPos.x + (targetX - startPos.x) * eased;
const y = startPos.y + (targetY - startPos.y) * eased;
peel.setPeelPosition(x, y);
currentPos = { x, y };
if (firstFrame && showLayers) {
firstFrame = false;
const capturedId = thisAnimationId;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
if (capturedId === animationId) {
isActive = true;
}
});
});
}
if (progress < 1) {
requestAnimationFrame(animate);
} else {
const rest = getRestPosition();
if (Math.abs(x - rest.x) < 1 && Math.abs(y - rest.y) < 1) {
isActive = false;
}
if (callback) callback();
}
}
requestAnimationFrame(animate);
}
function handleClick() {
if (!peelOnClick || !peel) return;
isPeeled = !isPeeled;
if (isPeeled) {
const target = getPeeledPosition();
animateTo(target.x, target.y, peelAwayDuration, () => {
onPeelComplete();
});
} else {
const target = isHovering ? getHoverPosition() : getRestPosition();
animateTo(target.x, target.y, peelAwayDuration);
}
}
function handleMouseEnter() {
if (!peel || !peelOnHover || isPeeled) return;
isHovering = true;
const pos = getHoverPosition();
animateTo(pos.x, pos.y, 200);
}
function handleMouseLeave() {
if (!peel || !peelOnHover || isPeeled) return;
isHovering = false;
const pos = getRestPosition();
animateTo(pos.x, pos.y, 200, null, false);
}
onMount(async () => {
const Peel = (await import('$lib/peel.js')).default;
if (!Peel || !container) return;
cornerValue = typeof corner === 'string' ? cornerMap[corner] ?? 4 : corner;
width = container.offsetWidth;
height = container.offsetHeight;
peel = new Peel(container, {
corner: cornerValue,
setPeelOnInit: false,
...options
});
currentPos = { x: peel.corner.x, y: peel.corner.y };
peel.setPeelPosition(currentPos.x, currentPos.y);
if (dragEnabled) {
peel.handleDrag(function(evt, x, y) {
this.setPeelPosition(x, y);
onPeel({ x, y, amountClipped: this.getAmountClipped() });
});
}
return () => {
if (peel) {
peel.removeEvents();
}
};
});
export function getPeel() {
return peel;
}
export function setPeelPosition(x, y) {
if (peel) peel.setPeelPosition(x, y);
}
export function peelAway() {
if (!peel) return;
isPeeled = true;
const target = getPeeledPosition();
animateTo(target.x, target.y, peelAwayDuration, onPeelComplete);
}
export function reset() {
if (!peel) return;
isPeeled = false;
const target = getRestPosition();
animateTo(target.x, target.y, peelAwayDuration);
}
</script>
<div
bind:this={container}
class="peelable {className}"
class:peelable-active={isActive || isPeeled}
style="--peel-border-radius: {borderRadius}"
onmouseenter={handleMouseEnter}
onmouseleave={handleMouseLeave}
onclick={handleClick}
role="button"
tabindex="0"
>
<div class="peel-top">
{#if topContent}
{@render topContent()}
{/if}
</div>
<div class="peel-back">
{#if backContent}
{@render backContent()}
{/if}
</div>
<div class="peel-bottom">
{#if bottomContent}
{@render bottomContent()}
{/if}
</div>
</div>
<style>
.peelable {
position: relative;
}
.peelable :global(.peel-top),
.peelable :global(.peel-back),
.peelable :global(.peel-bottom) {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: var(--peel-border-radius, 0);
overflow: hidden;
}
.peelable :global(.peel-back),
.peelable :global(.peel-bottom),
.peelable :global(.peel-top-shadow),
.peelable :global(.peel-back-shadow),
.peelable :global(.peel-back-reflection),
.peelable :global(.peel-bottom-shadow) {
opacity: 0;
pointer-events: none;
}
.peelable.peelable-active :global(.peel-back),
.peelable.peelable-active :global(.peel-bottom),
.peelable.peelable-active :global(.peel-top-shadow),
.peelable.peelable-active :global(.peel-back-shadow),
.peelable.peelable-active :global(.peel-back-reflection),
.peelable.peelable-active :global(.peel-bottom-shadow) {
opacity: 1;
pointer-events: auto;
}
.peelable :global(.peel-layer) {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
-webkit-transform-origin: top left;
-moz-transform-origin: top left;
transform-origin: top left;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.peelable :global(.peel-svg-clip-element) {
position: absolute;
top: -10000px;
left: -10000px;
width: 1px;
height: 1px;
opacity: 0;
}
</style>

987
src/lib/peel.js Normal file
View file

@ -0,0 +1,987 @@
(function(win) {
var PRECISION = 1e2;
var VENDOR_PREFIXES = ['webkit','moz', ''];
var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
var CSS_PREFIX = 'peel-';
var clipProperty, transformProperty, boxShadowProperty, filterProperty;
var backgroundGradientSupport;
var docEl = document.documentElement;
var style = docEl.style;
function getCssProperty(name) {
var prefix, str;
for (var i = 0; i < VENDOR_PREFIXES.length; i++) {
prefix = VENDOR_PREFIXES[i];
str = prefix ? prefix + capitalize(name) : name;
if (str in style) {
return str;
}
}
}
function setCssProperties() {
clipProperty = getCssProperty('clipPath');
transformProperty = getCssProperty('transform');
boxShadowProperty = getCssProperty('boxShadow');
filterProperty = getCssProperty('filter');
setBackgroundGradientSupport();
Peel.supported = !!(clipProperty && transformProperty);
Peel.effectsSupported = backgroundGradientSupport;
}
function setBackgroundGradientSupport() {
var el = document.createElement('div');
var style = el.style;
style.cssText = 'background:linear-gradient(45deg,,white);';
backgroundGradientSupport = (style.backgroundImage || '').indexOf('gradient') > -1;
}
function round(n) {
return Math.round(n * PRECISION) / PRECISION;
}
function clamp(n) {
return Math.max(0, Math.min(1, n));
}
function normalize(n, min, max) {
return (n - min) / (max - min);
}
function distribute(t, mult) {
return (mult || 1) * 2 * (.5 - Math.abs(t - .5));
}
function capitalize(str) {
return str.slice(0,1).toUpperCase() + str.slice(1);
}
function camelize(str) {
return str.replace(/-(\w)/g, function(a, b) {
return b.toUpperCase();
});
}
function prefix(str) {
return CSS_PREFIX + str;
}
function setCSSClip(el, clip) {
el.style[clipProperty] = clip;
}
function setTransform(el, t) {
el.style[transformProperty] = t;
}
function setBoxShadow(el, x, y, blur, spread, intensity) {
el.style[boxShadowProperty] = getShadowCss(x, y, blur, spread, intensity);
}
function setDropShadow(el, x, y, blur, intensity) {
el.style[filterProperty] = 'drop-shadow(' + getShadowCss(x, y, blur, null, intensity) + ')';
}
function getShadowCss(x, y, blur, spread, intensity) {
return round(x) + 'px ' +
round(y) + 'px ' +
round(blur) + 'px ' +
(spread ? round(spread) + 'px ' : '') +
'rgba(0,0,0,' + round(intensity) + ')';
}
function setOpacity(el, t) {
el.style.opacity = t;
}
function setBackgroundGradient(el, rotation, stops) {
if (!backgroundGradientSupport) return;
var css;
if (stops.length === 0) {
css = 'none';
} else {
css = 'linear-gradient(' + round(rotation) + 'deg,' + stops.join(',') + ')';
}
el.style.backgroundImage = css;
}
function addEvent(el, type, fn) {
el.addEventListener(type, fn)
}
function removeEvent(el, type, fn) {
el.removeEventListener(type, fn);
}
function getEventCoordinates(evt, el) {
var pos = evt.changedTouches ? evt.changedTouches[0] : evt;
return {
'x': pos.clientX - el.offsetLeft + window.scrollX,
'y': pos.clientY - el.offsetTop + window.scrollY
}
}
function bindWithEvent(fn, scope, arg1, arg2) {
return function(evt) {
fn.call(scope, evt, arg1, arg2);
}
}
function getBlackStop(a, pos) {
return getColorStop(0, 0, 0, a, pos);
}
function getWhiteStop(a, pos) {
return getColorStop(255, 255, 255, a, pos);
}
function getColorStop(r, g, b, a, pos) {
a = round(clamp(a));
return 'rgba('+ r +','+ g +','+ b +','+ a +') ' + round(pos * 100) + '%';
}
function getElement(obj, node) {
if (typeof obj === 'string') {
obj = (node || document).querySelector(obj);
}
return obj;
}
function createElement(parent, className) {
var el = document.createElement('div');
addClass(el, className);
parent.appendChild(el);
return el;
}
function removeClass(el, str) {
el.classList.remove(str);
}
function addClass(el, str) {
el.classList.add(str);
}
function getZIndex(el) {
return el.style.zIndex;
}
function setZIndex(el, index) {
el.style.zIndex = index;
}
function createSVGElement(tag, parent, attributes) {
parent = parent || docEl;
var el = document.createElementNS(SVG_NAMESPACE, tag);
parent.appendChild(el);
for (var key in attributes) {
if (!attributes.hasOwnProperty(key)) continue;
setSVGAttribute(el, key, attributes[key]);
}
return el;
}
function setSVGAttribute(el, key, value) {
el.setAttributeNS(null, key, value);
}
function Peel (el, opt) {
this.setOptions(opt);
this.el = getElement(el, docEl);
this.constraints = [];
this.events = [];
this.setupLayers();
this.setupDimensions();
this.setCorner(this.getOption('corner'));
this.setMode(this.getOption('mode'));
this.init();
}
Peel.Corners = {
TOP_LEFT: 0x0,
TOP_RIGHT: 0x1,
BOTTOM_LEFT: 0x2,
BOTTOM_RIGHT: 0x3
}
Peel.Defaults = {
'topShadow': true,
'topShadowBlur': 5,
'topShadowAlpha': .5,
'topShadowOffsetX': 0,
'topShadowOffsetY': 1,
'topShadowCreatesShape': true,
'backReflection': false,
'backReflectionSize': .02,
'backReflectionOffset': 0,
'backReflectionAlpha': .15,
'backReflectionDistribute': true,
'backShadow': true,
'backShadowSize': .04,
'backShadowOffset': 0,
'backShadowAlpha': .1,
'backShadowDistribute': true,
'bottomShadow': true,
'bottomShadowSize': 1.5,
'bottomShadowOffset': 0,
'bottomShadowDarkAlpha': .7,
'bottomShadowLightAlpha': .1,
'bottomShadowDistribute': true,
'setPeelOnInit': true,
'clippingBoxScale': 4,
'flipConstraintOffset': 5,
'dragPreventsDefault': true
}
Peel.prototype.setCorner = function() {
var args = arguments;
if (args[0] === undefined) {
args = [Peel.Corners.BOTTOM_RIGHT];
} else if (args[0].length) {
args = args[0];
}
this.corner = this.getPointOrCorner(args);
}
Peel.prototype.setMode = function(mode) {
if (mode === 'book') {
this.addPeelConstraint(Peel.Corners.BOTTOM_LEFT);
this.addPeelConstraint(Peel.Corners.TOP_LEFT);
this.setOption('backReflection', false);
this.setOption('backShadowDistribute', false);
this.setOption('bottomShadowDistribute', false);
} else if (mode === 'calendar') {
this.addPeelConstraint(Peel.Corners.TOP_RIGHT);
this.addPeelConstraint(Peel.Corners.TOP_LEFT);
}
}
Peel.prototype.setPeelPath = function(x1, y1) {
var args = arguments, p1, p2, c1, c2;
p1 = new Point(x1, y1);
if (args.length === 4) {
p2 = new Point(args[2], args[3]);
this.path = new LineSegment(p1, p2);
} else if (args.length === 8) {
c1 = new Point(args[2], args[3]);
c2 = new Point(args[4], args[5]);
p2 = new Point(args[6], args[7]);
this.path = new BezierCurve(p1, c1, c2, p2);
}
}
Peel.prototype.handleDrag = function(fn, el) {
this.dragHandler = fn;
this.setupDragEvents(el);
}
Peel.prototype.handlePress = function(fn, el) {
this.pressHandler = fn;
this.setupDragEvents(el);
}
Peel.prototype.setupDragEvents = function(el) {
var self = this, isDragging, moveName, endName;
if (this.dragEventsSetup) {
return;
}
el = el || this.el;
function dragStart (touch, evt) {
if (self.getOption('dragPreventsDefault')) {
evt.preventDefault();
}
moveName = touch ? 'touchmove' : 'mousemove';
endName = touch ? 'touchend' : 'mouseup';
addEvent(docEl, moveName, dragMove);
addEvent(docEl, endName, dragEnd);
isDragging = false;
}
function dragMove (evt) {
if (self.dragHandler) {
callHandler(self.dragHandler, evt);
}
isDragging = true;
}
function dragEnd(evt) {
if (!isDragging && self.pressHandler) {
callHandler(self.pressHandler, evt);
}
removeEvent(docEl, moveName, dragMove);
removeEvent(docEl, endName, dragEnd);
}
function callHandler(fn, evt) {
var coords = getEventCoordinates(evt, self.el);
fn.call(self, evt, coords.x, coords.y);
}
this.addEvent(el, 'mousedown', dragStart.bind(this, false));
this.addEvent(el, 'touchstart', dragStart.bind(this, true));
this.dragEventsSetup = true;
}
Peel.prototype.removeEvents = function() {
this.events.forEach(function(e, i) {
removeEvent(e.el, e.type, e.handler);
});
this.events = [];
}
Peel.prototype.setTimeAlongPath = function(t) {
t = clamp(t);
var point = this.path.getPointForTime(t);
this.timeAlongPath = t;
this.setPeelPosition(point.x, point.y);
}
Peel.prototype.setFadeThreshold = function(n) {
this.fadeThreshold = n;
}
Peel.prototype.setPeelPosition = function() {
var pos = this.getPointOrCorner(arguments);
pos = this.getConstrainedPeelPosition(pos);
if (!pos) {
return;
}
this.peelLineSegment = this.getPeelLineSegment(pos);
this.peelLineRotation = this.peelLineSegment.getAngle();
this.setClipping();
this.setBackTransform(pos);
this.setEffects();
}
Peel.prototype.addPeelConstraint = function() {
var p = this.getPointOrCorner(arguments);
var radius = this.corner.subtract(p).getLength();
this.constraints.push(new Circle(p, radius));
this.calculateFlipConstraint();
}
Peel.prototype.setOption = function(key, value) {
this.options[key] = value;
}
Peel.prototype.getOption = function(key) {
return this.options[camelize(key)];
}
Peel.prototype.getAmountClipped = function() {
var topArea = this.getTopClipArea();
var totalArea = this.width * this.height;
return normalize(topArea, totalArea, 0);
}
Peel.prototype.addEvent = function(el, type, fn) {
addEvent(el, type, fn);
this.events.push({
el: el,
type: type,
handler: fn
});
return fn;
}
Peel.prototype.getTopClipArea = function() {
var top = new Polygon();
this.elementBox.forEach(function(side) {
this.distributeLineByPeelLine(side, top);
}, this);
return Polygon.getArea(top.getPoints());
}
Peel.prototype.calculateFlipConstraint = function() {
var corner = this.corner, arr = this.constraints.concat();
this.flipConstraint = arr.sort(function(a, b) {
var aY = corner.y - a.center.y;
var bY = corner.y - b.center.y;
return a - b;
})[0];
}
Peel.prototype.dragStart = function(evt, type, fn) {}
Peel.prototype.fireHandler = function(evt, fn) {
var coords = getEventCoordinates(evt, this.el);
fn.call(this, evt, coords.x, coords.y);
}
Peel.prototype.setClipping = function() {
var top = new Polygon();
var back = new Polygon();
this.clippingBox.forEach(function(side) {
this.distributeLineByPeelLine(side, top, back);
}, this);
this.topClip.setPoints(top.getPoints());
this.backClip.setPoints(back.getPoints());
}
Peel.prototype.distributeLineByPeelLine = function(seg, poly1, poly2) {
var intersect = this.peelLineSegment.getIntersectPoint(seg);
this.distributePointByPeelLine(seg.p1, poly1, poly2);
this.distributePointByPeelLine(intersect, poly1, poly2);
}
Peel.prototype.distributePointByPeelLine = function(p, poly1, poly2) {
if (!p) return;
var d = this.peelLineSegment.getPointDeterminant(p);
if (d <= 0) {
poly1.addPoint(p);
}
if (d >= 0 && poly2) {
poly2.addPoint(this.flipPointHorizontally(p));
}
}
Peel.prototype.setOptions = function(opt) {
var options = opt || {}, defaults = Peel.Defaults;
for (var key in defaults) {
if (!defaults.hasOwnProperty(key) || key in options) {
continue;
}
options[key] = defaults[key];
}
this.options = options;
}
Peel.prototype.findOrCreateLayer = function(id, parent, zIndex) {
var optId = id + '-element';
var domId = prefix(id);
var el = getElement(this.getOption(optId) || '.' + domId, parent);
if (!el) {
el = createElement(parent, domId);
}
addClass(el, prefix('layer'));
setZIndex(el, zIndex);
return el;
}
Peel.prototype.getPointOrCorner = function(args) {
if (args.length === 2) {
return new Point(args[0], args[1]);
} else if(typeof args[0] === 'number') {
return this.getCornerPoint(args[0]);
}
return args[0];
}
Peel.prototype.getCornerPoint = function(id) {
var x = +!!(id & 1) * this.width;
var y = +!!(id & 2) * this.height;
return new Point(x, y);
}
Peel.prototype.getOptionalShape = function() {
var shapes = ['rect', 'polygon', 'path', 'circle'], found;
shapes.some(function(type) {
var attr = this.getOption(type), obj;
if (attr) {
obj = {};
obj.attributes = attr;
obj.type = type;
found = obj;
}
return found;
}, this);
return found;
}
Peel.prototype.setupLayers = function() {
var shape = this.getOptionalShape();
var topInnerLayer = this.topLayer = this.findOrCreateLayer('top', this.el, 2);
var backInnerLayer = this.backLayer = this.findOrCreateLayer('back', this.el, 3);
this.bottomLayer = this.findOrCreateLayer('bottom', this.el, 1);
if (shape) {
this.topLayer = this.wrapShapeLayer(this.topLayer, 'top-outer-clip');
this.backLayer = this.wrapShapeLayer(this.backLayer, 'back-outer-clip');
this.topShapeClip = new SVGClip(topInnerLayer, shape);
this.backShapeClip = new SVGClip(backInnerLayer, shape);
this.bottomShapeClip = new SVGClip(this.bottomLayer, shape);
if (this.getOption('topShadowCreatesShape')) {
this.topShadowElement = this.setupDropShadow(shape, topInnerLayer);
}
} else {
this.topShadowElement = this.findOrCreateLayer('top-shadow', topInnerLayer, 1);
}
this.topClip = new SVGClip(this.topLayer);
this.backClip = new SVGClip(this.backLayer);
this.backShadowElement = this.findOrCreateLayer('back-shadow', backInnerLayer, 1);
this.backReflectionElement = this.findOrCreateLayer('back-reflection', backInnerLayer, 2);
this.bottomShadowElement = this.findOrCreateLayer('bottom-shadow', this.bottomLayer, 1);
this.usesBoxShadow = !shape;
}
Peel.prototype.setupDropShadow = function(shape, parent) {
var svg = createSVGElement('svg', parent, {
'class': prefix('layer')
});
createSVGElement(shape.type, svg, shape.attributes);
return svg;
}
Peel.prototype.wrapShapeLayer = function(el, id) {
var zIndex = getZIndex(el);
addClass(el, prefix('shape-layer'));
var outerLayer = this.findOrCreateLayer(id, this.el, zIndex);
outerLayer.appendChild(el);
return outerLayer;
}
Peel.prototype.setupDimensions = function() {
this.width = this.el.offsetWidth;
this.height = this.el.offsetHeight;
this.center = new Point(this.width / 2, this.height / 2);
this.elementBox = this.getScaledBox(1);
this.clippingBox = this.getScaledBox(this.getOption('clippingBoxScale'));
}
Peel.prototype.getScaledBox = function(scale) {
var brScale = scale;
var tlScale = scale - 1;
var tl = new Point(-this.width * tlScale, -this.height * tlScale);
var tr = new Point( this.width * brScale, -this.height * tlScale);
var br = new Point( this.width * brScale, this.height * brScale);
var bl = new Point(-this.width * tlScale, this.height * brScale);
return [
new LineSegment(tl, tr),
new LineSegment(tr, br),
new LineSegment(br, bl),
new LineSegment(bl, tl)
];
}
Peel.prototype.getConstrainedPeelPosition = function(pos) {
this.constraints.forEach(function(area) {
var offset = this.getFlipConstraintOffset(area, pos);
if (offset) {
area = new Circle(area.center, area.radius - offset);
}
pos = area.constrainPoint(pos);
}, this);
return pos;
}
Peel.prototype.getFlipConstraintOffset = function(area, pos) {
var offset = this.getOption('flipConstraintOffset');
if (area === this.flipConstraint && offset) {
var cornerToCenter = this.corner.subtract(this.center);
var cornerToConstraint = this.corner.subtract(area.center);
var baseAngle = cornerToConstraint.getAngle();
var nCornerToConstraint = cornerToConstraint.rotate(-baseAngle);
var nPosToConstraint = pos.subtract(area.center).rotate(-baseAngle);
if (cornerToCenter.x * cornerToCenter.y < 0) {
nPosToConstraint.y *= -1;
}
if (nPosToConstraint.x > 0 && nPosToConstraint.y > 0) {
return normalize(nPosToConstraint.getAngle(), 45, 0) * offset;
}
}
}
Peel.prototype.getPeelLineSegment = function(point) {
var halfToCorner = this.corner.subtract(point).scale(.5);
var midpoint = point.add(halfToCorner);
if (halfToCorner.x === 0 && halfToCorner.y === 0) {
halfToCorner = point.subtract(this.center);
}
var l = halfToCorner.getLength()
var mult = (Math.max(this.width, this.height) / l) * 10;
var half = halfToCorner.rotate(-90).scale(mult);
var p1 = midpoint.add(half);
var p2 = midpoint.subtract(half);
return new LineSegment(p1, p2);
}
Peel.prototype.setBackTransform = function(pos) {
var mirroredCorner = this.flipPointHorizontally(this.corner);
var r = (this.peelLineRotation - 90) * 2;
var t = pos.subtract(mirroredCorner.rotate(r));
var css = 'translate('+ round(t.x) +'px, '+ round(t.y) +'px) rotate('+ round(r) +'deg)';
setTransform(this.backLayer, css);
setTransform(this.topShadowElement, css);
}
Peel.prototype.getPeelLineDistance = function() {
var cornerId, opposingCornerId, corner, opposingCorner;
if (this.peelLineRotation < 90) {
cornerId = Peel.Corners.TOP_RIGHT;
opposingCornerId = Peel.Corners.BOTTOM_LEFT;
} else if (this.peelLineRotation < 180) {
cornerId = Peel.Corners.BOTTOM_RIGHT;
opposingCornerId = Peel.Corners.TOP_LEFT;
} else if (this.peelLineRotation < 270) {
cornerId = Peel.Corners.BOTTOM_LEFT;
opposingCornerId = Peel.Corners.TOP_RIGHT;
} else if (this.peelLineRotation < 360) {
cornerId = Peel.Corners.TOP_LEFT;
opposingCornerId = Peel.Corners.BOTTOM_RIGHT;
}
corner = this.getCornerPoint(cornerId);
opposingCorner = this.getCornerPoint(opposingCornerId);
var cornerToCorner = new LineSegment(corner, opposingCorner).scale(2);
var intersect = this.peelLineSegment.getIntersectPoint(cornerToCorner);
if (!intersect) {
return 2;
}
var distanceToPeelLine = corner.subtract(intersect).getLength();
var totalDistance = corner.subtract(opposingCorner).getLength();
return (distanceToPeelLine / totalDistance);
}
Peel.prototype.setEffects = function() {
var t = this.getPeelLineDistance();
this.setTopShadow(t);
this.setBackShadow(t);
this.setBackReflection(t);
this.setBottomShadow(t);
this.setFade();
}
Peel.prototype.setTopShadow = function(t) {
if (!this.getOption('topShadow')) {
return;
}
var sBlur = this.getOption('topShadowBlur');
var sX = this.getOption('topShadowOffsetX');
var sY = this.getOption('topShadowOffsetY');
var alpha = this.getOption('topShadowAlpha');
var sAlpha = this.exponential(t, 5, alpha);
if (this.usesBoxShadow) {
setBoxShadow(this.topShadowElement, sX, sY, sBlur, 0, sAlpha);
} else {
setDropShadow(this.topShadowElement, sX, sY, sBlur, sAlpha);
}
}
Peel.prototype.distributeOrLinear = function(n, dist, mult) {
if (dist) {
return distribute(n, mult);
} else {
return n * mult;
}
}
Peel.prototype.exponential = function(n, exp, mult) {
return mult * clamp(Math.pow(1 + n, exp) - 1);
}
Peel.prototype.setBackReflection = function(t) {
var stops = [];
if (this.canSetLinearEffect('backReflection', t)) {
var rDistribute = this.getOption('backReflectionDistribute');
var rSize = this.getOption('backReflectionSize');
var rOffset = this.getOption('backReflectionOffset');
var rAlpha = this.getOption('backReflectionAlpha');
var reflectionSize = this.distributeOrLinear(t, rDistribute, rSize);
var rStop = t - rOffset;
var rMid = rStop - reflectionSize;
var rStart = rMid - reflectionSize;
stops.push(getWhiteStop(0, 0));
stops.push(getWhiteStop(0, rStart));
stops.push(getWhiteStop(rAlpha, rMid));
stops.push(getWhiteStop(0, rStop));
}
setBackgroundGradient(this.backReflectionElement, 180 - this.peelLineRotation, stops);
}
Peel.prototype.setBackShadow = function(t) {
var stops = [];
if (this.canSetLinearEffect('backShadow', t)) {
var sSize = this.getOption('backShadowSize');
var sOffset = this.getOption('backShadowOffset');
var sAlpha = this.getOption('backShadowAlpha');
var sDistribute = this.getOption('backShadowDistribute');
var shadowSize = this.distributeOrLinear(t, sDistribute, sSize);
var shadowStop = t - sOffset;
var shadowMid = shadowStop - shadowSize;
var shadowStart = shadowMid - shadowSize;
stops.push(getBlackStop(0, 0));
stops.push(getBlackStop(0, shadowStart));
stops.push(getBlackStop(sAlpha, shadowMid));
stops.push(getBlackStop(sAlpha, shadowStop));
}
setBackgroundGradient(this.backShadowElement, 180 - this.peelLineRotation, stops);
}
Peel.prototype.setBottomShadow = function(t) {
var stops = [];
if (this.canSetLinearEffect('bottomShadow', t)) {
var sSize = this.getOption('bottomShadowSize');
var offset = this.getOption('bottomShadowOffset');
var darkAlpha = this.getOption('bottomShadowDarkAlpha');
var lightAlpha = this.getOption('bottomShadowLightAlpha');
var sDistribute = this.getOption('bottomShadowDistribute');
var darkShadowStart = t - (.025 - offset);
var midShadowStart = darkShadowStart - (this.distributeOrLinear(t, sDistribute, .03) * sSize) - offset;
var lightShadowStart = midShadowStart - ((.02 * sSize) - offset);
stops = [
getBlackStop(0, 0),
getBlackStop(0, lightShadowStart),
getBlackStop(lightAlpha, midShadowStart),
getBlackStop(lightAlpha, darkShadowStart),
getBlackStop(darkAlpha, t)
];
}
setBackgroundGradient(this.bottomShadowElement, this.peelLineRotation + 180, stops);
}
Peel.prototype.canSetLinearEffect = function(name, t) {
return this.getOption(name) && t > 0;
}
Peel.prototype.setFade = function() {
var threshold = this.fadeThreshold, opacity = 1, n;
if (threshold) {
if (this.timeAlongPath !== undefined) {
n = this.timeAlongPath;
} else {
n = this.getAmountClipped();
}
if (n > threshold) {
opacity = (1 - n) / (1 - threshold);
}
setOpacity(this.topLayer, opacity);
setOpacity(this.backLayer, opacity);
setOpacity(this.bottomShadowElement, opacity);
}
}
Peel.prototype.flipPointHorizontally = function(p) {
return new Point(p.x - ((p.x - this.center.x) * 2), p.y);
}
Peel.prototype.init = function() {
if (this.getOption('setPeelOnInit')) {
this.setPeelPosition(this.corner);
}
addClass(this.el, prefix('ready'));
}
function SVGClip (el, shape) {
this.el = el;
this.shape = SVGClip.createClipPath(el, shape || {
'type': 'polygon'
});
setTransform(this.el, 'translate(0px,0px)');
}
SVGClip.getDefs = function() {
if (!this.defs) {
this.svg = createSVGElement('svg', null, {
'class': prefix('svg-clip-element')
});
this.defs = createSVGElement('defs', this.svg);
}
return this.defs;
}
SVGClip.createClipPath = function(el, obj) {
var id = SVGClip.getId();
var clipPath = createSVGElement('clipPath', this.getDefs());
var svgEl = createSVGElement(obj.type, clipPath, obj.attributes);
setSVGAttribute(clipPath, 'id', id);
setCSSClip(el, 'url(#' + id + ')');
return svgEl;
}
SVGClip.getId = function() {
if (!SVGClip.id) {
SVGClip.id = 1;
}
return 'svg-clip-' + SVGClip.id++;
}
SVGClip.prototype.setPoints = function(points) {
var str = points.map(function(p) {
return round(p.x) + ',' + round(p.y);
}).join(' ');
setSVGAttribute(this.shape, 'points', str);
}
function Circle (center, radius) {
this.center = center;
this.radius = radius;
}
Circle.prototype.containsPoint = function(p) {
if(this.boundingRectContainsPoint(p)) {
var dx = this.center.x - p.x;
var dy = this.center.y - p.y;
dx *= dx;
dy *= dy;
var distanceSquared = dx + dy;
var radiusSquared = this.radius * this.radius;
return distanceSquared <= radiusSquared;
}
return false;
}
Circle.prototype.boundingRectContainsPoint = function(p) {
return p.x >= this.center.x - this.radius && p.x <= this.center.x + this.radius &&
p.y >= this.center.y - this.radius && p.y <= this.center.y + this.radius;
}
Circle.prototype.constrainPoint = function(p) {
if (!this.containsPoint(p)) {
var rotation = p.subtract(this.center).getAngle();
p = this.center.add(new Point(this.radius, 0).rotate(rotation));
}
return p;
}
function Polygon() {
this.points = [];
}
Polygon.getArea = function(points) {
var sum1 = 0, sum2 = 0;
points.forEach(function(p, i, arr) {
var next = arr[(i + 1) % arr.length];
sum1 += (p.x * next.y);
sum2 += (p.y * next.x);
});
return (sum1 - sum2) / 2;
}
Polygon.prototype.addPoint = function(point) {
this.points.push(point);
}
Polygon.prototype.getPoints = function() {
return this.points;
}
function BezierCurve (p1, c1, c2, p2) {
this.p1 = p1;
this.c1 = c1;
this.p2 = p2;
this.c2 = c2;
}
BezierCurve.prototype.getPointForTime = function(t) {
var b0 = Math.pow(1 - t, 3);
var b1 = 3 * t * Math.pow(1 - t, 2);
var b2 = 3 * Math.pow(t, 2) * (1 - t);
var b3 = Math.pow(t, 3);
var x = (b0 * this.p1.x) + (b1 * this.c1.x) + (b2 * this.c2.x) + (b3 * this.p2.x)
var y = (b0 * this.p1.y) + (b1 * this.c1.y) + (b2 * this.c2.y) + (b3 * this.p2.y)
return new Point(x, y);
}
function LineSegment (p1, p2) {
this.p1 = p1;
this.p2 = p2;
}
LineSegment.EPSILON = 1e-6;
LineSegment.prototype.getPointForTime = function(t) {
return this.p1.add(this.getVector().scale(t));
}
LineSegment.prototype.scale = function(n) {
var half = 1 + (n / 2);
var p1 = this.p1.add(this.p2.subtract(this.p1).scale(n));
var p2 = this.p2.add(this.p1.subtract(this.p2).scale(n));
return new LineSegment(p1, p2);
}
LineSegment.prototype.getPointDeterminant = function(p) {
var d = ((p.x - this.p1.x) * (this.p2.y - this.p1.y)) - ((p.y - this.p1.y) * (this.p2.x - this.p1.x));
if (d > -LineSegment.EPSILON && d < LineSegment.EPSILON) {
d = 0;
}
return d;
}
LineSegment.prototype.getIntersectPoint = function(seg2) {
var seg1 = this;
function crossProduct(p1, p2) {
return p1.x * p2.y - p1.y * p2.x;
}
var r = seg1.p2.subtract(seg1.p1);
var s = seg2.p2.subtract(seg2.p1);
var uNumerator = crossProduct(seg2.p1.subtract(seg1.p1), r);
var denominator = crossProduct(r, s);
if (denominator == 0) {
return null;
}
var u = uNumerator / denominator;
var t = crossProduct(seg2.p1.subtract(seg1.p1), s) / denominator;
if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) {
return seg1.p1.add(r.scale(t));
}
return null;
}
LineSegment.prototype.getAngle = function() {
return this.getVector().getAngle();
}
LineSegment.prototype.getVector = function() {
if (!this.vector) {
this.vector = this.p2.subtract(this.p1);
}
return this.vector;
}
function Point (x, y) {
this.x = x;
this.y = y;
}
Point.DEGREES_IN_RADIANS = 180 / Math.PI;
Point.degToRad = function(deg) {
return deg / Point.DEGREES_IN_RADIANS;
};
Point.radToDeg = function(rad) {
var deg = rad * Point.DEGREES_IN_RADIANS;
while(deg < 0) deg += 360;
return deg;
};
Point.vector = function(deg, len) {
var rad = Point.degToRad(deg);
return new Point(Math.cos(rad) * len, Math.sin(rad) * len);
};
Point.prototype.add = function(p) {
return new Point(this.x + p.x, this.y + p.y);
};
Point.prototype.subtract = function(p) {
return new Point(this.x - p.x, this.y - p.y);
};
Point.prototype.scale = function(n) {
return new Point(this.x * n, this.y * n);
};
Point.prototype.getLength = function() {
return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
};
Point.prototype.getAngle = function() {
return Point.radToDeg(Math.atan2(this.y, this.x));
};
Point.prototype.setAngle = function(deg) {
return Point.vector(deg, this.getLength());
};
Point.prototype.rotate = function(deg) {
return this.setAngle(this.getAngle() + deg);
};
setCssProperties();
win.Peel = Peel;
})(typeof window !== 'undefined' ? window : {});
export default typeof window !== 'undefined' ? window.Peel : null;

View file

@ -1,3 +1,114 @@
<h1>Stickers</h1>
<p>The magic of hackclub is here! </p>
<p>Get free stickers, show off your collection, trade or hack to collect rare stickers.</p>
<script>
import Peelable from '$lib/components/Peelable.svelte';
function handleLogin() {
window.location.href = '/login';
}
</script>
<div class="container">
<h1>Stickers</h1>
<p>Manage everything sticky! Get free stickers for signing up, hack or trade to earn rare stickers and certify your collection.</p>
<Peelable
class="login-sticker"
corner="bottom-right"
peelOnHover={true}
hoverPeelAmount={0.4}
peelOnClick={true}
peelAwayDuration={1000}
onPeelComplete={handleLogin}
borderRadius="0.5rem"
>
{#snippet topContent()}
<div class="sticker-face">Sign in!</div>
{/snippet}
{#snippet backContent()}
<div class="sticker-back"></div>
{/snippet}
{#snippet bottomContent()}
<div class="sticker-surface">
<span></span>
</div>
{/snippet}
</Peelable>
<p>*footer joke here</p>
</div>
<style>
@font-face {
font-family: 'Departure Mono';
src: url('$lib/assets/fonts/DepartureMono-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
text-align: center;
font-family: 'Departure Mono', monospace;
padding: 2rem;
background: #eeeeee;
}
h1 {
font-size: 10rem;
margin-bottom: 1rem;
font-weight: 400;
}
p {
font-size: 2rem;
max-width: 60vw;
margin: 0.5rem 0;
opacity: 0.8;
}
:global(.login-sticker) {
width: clamp(150px, 20vw, 650px);
height: clamp(45px, 20vw, 120px);
margin: 2rem 0;
cursor: pointer;
}
.sticker-face {
width: 100%;
height: 100%;
background: #9cada6;
border-radius: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-family: 'Departure Mono', monospace;
font-weight: 400;
font-size: 3.1rem;
}
.sticker-back {
width: 100%;
height: 100%;
background: #d9c9b6;
border-radius: 0.5rem;
}
.sticker-surface {
width: 100%;
height: 100%;
background: #ffffff;
border-radius: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
color: #333;
font-family: 'Departure Mono', monospace;
font-size: 0.9rem;
}
</style>