|
|
/**
|
|
|
* Cropper recipe — the crop UI chrome: framed viewport, darken-outside mask,
|
|
|
* rule-of-thirds grid, and corner handles. The crop geometry is inline style on
|
|
|
* the Selection (set by soma); the image is the composed `<Image>`.
|
|
|
*
|
|
|
* Tokens: `--cropper-*` (public, themeable) from `recipes/base.ts`.
|
|
|
*/
|
|
|
|
|
|
[data-cropper] {
|
|
|
display: block;
|
|
|
inline-size: 100%;
|
|
|
}
|
|
|
|
|
|
[data-cropper][data-disabled] {
|
|
|
opacity: var(--opacity-disabled);
|
|
|
pointer-events: none;
|
|
|
}
|
|
|
|
|
|
[data-cropper-viewport] {
|
|
|
position: relative;
|
|
|
overflow: hidden;
|
|
|
inline-size: 100%;
|
|
|
/* aspect-ratio is set inline from the image's natural size. Fallback 1:1. */
|
|
|
aspect-ratio: 1;
|
|
|
border-radius: var(--cropper-viewport-radius);
|
|
|
background: var(--cropper-viewport-bg);
|
|
|
/* The image fills the viewport; no scrolling while dragging the selection. */
|
|
|
touch-action: none;
|
|
|
user-select: none;
|
|
|
}
|
|
|
|
|
|
.eidos-cropper-image {
|
|
|
position: absolute;
|
|
|
inset: 0;
|
|
|
inline-size: 100%;
|
|
|
block-size: 100%;
|
|
|
}
|
|
|
|
|
|
/* Stop the browser's native image drag-and-drop, which would cancel the pan. */
|
|
|
[data-cropper-viewport] img {
|
|
|
-webkit-user-drag: none;
|
|
|
user-select: none;
|
|
|
}
|
|
|
|
|
|
/* The crop rectangle. The huge box-shadow darkens everything outside it
|
|
|
(clipped by the viewport's overflow:hidden) — it follows border-radius, so a
|
|
|
round selection cuts a circular hole. */
|
|
|
[data-cropper-selection] {
|
|
|
position: absolute;
|
|
|
box-sizing: border-box;
|
|
|
border: var(--cropper-selection-border-width) solid var(--cropper-selection-border);
|
|
|
box-shadow: 0 0 0 9999px var(--cropper-mask-bg);
|
|
|
cursor: move;
|
|
|
touch-action: none;
|
|
|
}
|
|
|
|
|
|
[data-cropper-selection][data-shape='round'] {
|
|
|
border-radius: 50%;
|
|
|
}
|
|
|
|
|
|
[data-cropper-selection][data-dragging] {
|
|
|
cursor: grabbing;
|
|
|
}
|
|
|
|
|
|
/* When zoomed, the background image shows a `grab` cursor (drag it to pan / set
|
|
|
the focus). The selection keeps `move` — it repositions the crop area. */
|
|
|
.eidos-cropper-viewport.is-zoomed .eidos-cropper-image {
|
|
|
cursor: grab;
|
|
|
}
|
|
|
.eidos-cropper-viewport.is-zoomed .eidos-cropper-image:active {
|
|
|
cursor: grabbing;
|
|
|
}
|
|
|
|
|
|
/* Rule-of-thirds — four thin lines at 1/3 and 2/3 on each axis. */
|
|
|
[data-cropper-grid] {
|
|
|
position: absolute;
|
|
|
inset: 0;
|
|
|
pointer-events: none;
|
|
|
background:
|
|
|
linear-gradient(var(--cropper-grid-line), var(--cropper-grid-line)) 33.33% 0 / 1px 100% no-repeat,
|
|
|
linear-gradient(var(--cropper-grid-line), var(--cropper-grid-line)) 66.66% 0 / 1px 100% no-repeat,
|
|
|
linear-gradient(var(--cropper-grid-line), var(--cropper-grid-line)) 0 33.33% / 100% 1px no-repeat,
|
|
|
linear-gradient(var(--cropper-grid-line), var(--cropper-grid-line)) 0 66.66% / 100% 1px no-repeat;
|
|
|
}
|
|
|
|
|
|
/* The thirds grid reads as noise inside a circular crop — hide it there. */
|
|
|
[data-cropper-selection][data-shape='round'] [data-cropper-grid] {
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
[data-cropper-handle] {
|
|
|
position: absolute;
|
|
|
box-sizing: border-box;
|
|
|
inline-size: var(--cropper-handle-size);
|
|
|
block-size: var(--cropper-handle-size);
|
|
|
border: var(--cropper-handle-border-width) solid var(--cropper-handle-border);
|
|
|
border-radius: 50%;
|
|
|
background: var(--cropper-handle-bg);
|
|
|
touch-action: none;
|
|
|
}
|
|
|
|
|
|
[data-cropper-handle][data-corner='nw'] {
|
|
|
inset-block-start: 0;
|
|
|
inset-inline-start: 0;
|
|
|
transform: translate(-50%, -50%);
|
|
|
cursor: nwse-resize;
|
|
|
}
|
|
|
[data-cropper-handle][data-corner='ne'] {
|
|
|
inset-block-start: 0;
|
|
|
inset-inline-end: 0;
|
|
|
transform: translate(50%, -50%);
|
|
|
cursor: nesw-resize;
|
|
|
}
|
|
|
[data-cropper-handle][data-corner='se'] {
|
|
|
inset-block-end: 0;
|
|
|
inset-inline-end: 0;
|
|
|
transform: translate(50%, 50%);
|
|
|
cursor: nwse-resize;
|
|
|
}
|
|
|
[data-cropper-handle][data-corner='sw'] {
|
|
|
inset-block-end: 0;
|
|
|
inset-inline-start: 0;
|
|
|
transform: translate(-50%, 50%);
|
|
|
cursor: nesw-resize;
|
|
|
}
|
|
|
|
|
|
/* Edge handles — centered on each side. */
|
|
|
[data-cropper-handle][data-corner='n'] {
|
|
|
inset-block-start: 0;
|
|
|
inset-inline-start: 50%;
|
|
|
transform: translate(-50%, -50%);
|
|
|
cursor: ns-resize;
|
|
|
}
|
|
|
[data-cropper-handle][data-corner='s'] {
|
|
|
inset-block-end: 0;
|
|
|
inset-inline-start: 50%;
|
|
|
transform: translate(-50%, 50%);
|
|
|
cursor: ns-resize;
|
|
|
}
|
|
|
[data-cropper-handle][data-corner='e'] {
|
|
|
inset-block-start: 50%;
|
|
|
inset-inline-end: 0;
|
|
|
transform: translate(50%, -50%);
|
|
|
cursor: ew-resize;
|
|
|
}
|
|
|
[data-cropper-handle][data-corner='w'] {
|
|
|
inset-block-start: 50%;
|
|
|
inset-inline-start: 0;
|
|
|
transform: translate(-50%, -50%);
|
|
|
cursor: ew-resize;
|
|
|
}
|
|
|
|
|
|
/* ── Zoom controls (− slider + reset) ───────────────────────────────── */
|
|
|
.eidos-cropper-zoom {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: var(--cropper-zoom-gap);
|
|
|
margin-block-start: var(--space-2);
|
|
|
}
|
|
|
|
|
|
.eidos-cropper-zoom [data-slider] {
|
|
|
flex: 1 1 auto;
|
|
|
}
|
|
|
|
|
|
.eidos-cropper-zoom-btn {
|
|
|
display: inline-flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
flex: 0 0 auto;
|
|
|
inline-size: var(--cropper-zoom-btn-size);
|
|
|
block-size: var(--cropper-zoom-btn-size);
|
|
|
border: var(--border-width) solid var(--cropper-zoom-btn-border);
|
|
|
border-radius: var(--radius-md);
|
|
|
background: var(--cropper-zoom-btn-bg);
|
|
|
color: var(--cropper-zoom-btn-fg);
|
|
|
font-size: var(--font-size-md);
|
|
|
line-height: 1;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
|
|
|
.eidos-cropper-zoom-btn:hover {
|
|
|
background: var(--color-surface-default);
|
|
|
}
|
|
|
|
|
|
.eidos-cropper-zoom-btn:focus-visible {
|
|
|
outline: var(--focus-ring-width) solid var(--color-focus-ring);
|
|
|
outline-offset: 1px;
|
|
|
}
|