react-3d-carousel-fullcontrol 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Carousel.jsx DELETED
@@ -1,649 +0,0 @@
1
- import { useEffect, useRef, useState, useCallback, memo } from "react";
2
- import { motion } from "framer-motion";
3
-
4
- // ─── Particle system ────────────────────────────────────────────────────────
5
- function Particles() {
6
- const count = 80;
7
- const particles = useRef(
8
- Array.from({ length: count }, (_, i) => ({
9
- id: i,
10
- x: Math.random() * 100,
11
- y: Math.random() * 100,
12
- size: Math.random() * 2.5 + 0.5,
13
- opacity: Math.random() * 0.5 + 0.1,
14
- dur: Math.random() * 12 + 8,
15
- delay: Math.random() * -20,
16
- }))
17
- ).current;
18
-
19
- return (
20
- <div
21
- style={{
22
- position: "absolute",
23
- inset: 0,
24
- overflow: "hidden",
25
- pointerEvents: "none",
26
- zIndex: 1,
27
- }}
28
- >
29
- {particles.map((p) => (
30
- <motion.div
31
- key={p.id}
32
- style={{
33
- position: "absolute",
34
- left: `${p.x}%`,
35
- top: `${p.y}%`,
36
- width: p.size,
37
- height: p.size,
38
- borderRadius: "50%",
39
- background: `rgba(180,160,255,${p.opacity})`,
40
- }}
41
- animate={{
42
- y: [0, -30, 0],
43
- opacity: [p.opacity, p.opacity * 2, p.opacity],
44
- }}
45
- transition={{
46
- duration: p.dur,
47
- repeat: Infinity,
48
- ease: "easeInOut",
49
- delay: p.delay,
50
- }}
51
- />
52
- ))}
53
- </div>
54
- );
55
- }
56
-
57
- // ─── Light blobs ────────────────────────────────────────────────────────────
58
-
59
- function LightBlobs({ mouseX, mouseY }) {
60
- return (
61
- <div
62
- style={{
63
- position: "absolute",
64
- inset: 0,
65
- pointerEvents: "none",
66
- zIndex: 0,
67
- overflow: "hidden",
68
- }}
69
- >
70
- <div
71
- style={{
72
- position: "absolute",
73
- width: 700,
74
- height: 700,
75
- borderRadius: "50%",
76
- left: mouseX - 350,
77
- top: mouseY - 350,
78
- background:
79
- "radial-gradient(circle, rgba(100,80,200,0.12) 0%, transparent 70%)",
80
- transition: "left 0.6s ease, top 0.6s ease",
81
- pointerEvents: "none",
82
- }}
83
- />
84
- <motion.div
85
- style={{
86
- position: "absolute",
87
- width: 600,
88
- height: 600,
89
- borderRadius: "50%",
90
- top: "-10%",
91
- left: "10%",
92
- background:
93
- "radial-gradient(circle, rgba(80,40,180,0.18) 0%, transparent 70%)",
94
- filter: "blur(60px)",
95
- }}
96
- animate={{ scale: [1, 1.15, 1], x: [0, 40, 0] }}
97
- transition={{ duration: 18, repeat: Infinity, ease: "easeInOut" }}
98
- />
99
- <motion.div
100
- style={{
101
- position: "absolute",
102
- width: 500,
103
- height: 500,
104
- borderRadius: "50%",
105
- bottom: "-5%",
106
- right: "5%",
107
- background:
108
- "radial-gradient(circle, rgba(40,100,200,0.14) 0%, transparent 70%)",
109
- filter: "blur(80px)",
110
- }}
111
- animate={{ scale: [1, 1.2, 1], y: [0, -50, 0] }}
112
- transition={{ duration: 14, repeat: Infinity, ease: "easeInOut", delay: 3 }}
113
- />
114
- <motion.div
115
- style={{
116
- position: "absolute",
117
- width: 400,
118
- height: 400,
119
- borderRadius: "50%",
120
- top: "40%",
121
- left: "40%",
122
- background:
123
- "radial-gradient(circle, rgba(160,60,220,0.08) 0%, transparent 70%)",
124
- filter: "blur(100px)",
125
- }}
126
- animate={{ scale: [1, 1.3, 1] }}
127
- transition={{ duration: 22, repeat: Infinity, ease: "easeInOut", delay: 7 }}
128
- />
129
- </div>
130
- );
131
- }
132
-
133
- // ─── Single carousel card ────────────────────────────────────────────────────
134
-
135
- const CarouselCard = memo(function CarouselCard({
136
- image,
137
- index,
138
- totalAngle,
139
- opacity,
140
- angleStep,
141
- radius,
142
- cardWidth,
143
- cardHeight,
144
- }) {
145
- const angle = index * angleStep;
146
- const [hovered, setHovered] = useState(false);
147
-
148
- const normalizedAngle = ((totalAngle + angle) % 360 + 360) % 360;
149
- const frontness = Math.max(0, Math.cos((normalizedAngle * Math.PI) / 180));
150
-
151
- return (
152
- <div
153
- style={{
154
- position: "absolute",
155
- width: `${cardWidth}px`,
156
- height: `${cardHeight}px`,
157
- left: `-${cardWidth / 2}px`,
158
- top: `-${cardHeight / 2}px`,
159
- transform: `rotateY(${angle}deg) translateZ(${radius}px)`,
160
- transformStyle: "preserve-3d",
161
- backfaceVisibility: "visible",
162
- WebkitBackfaceVisibility: "visible",
163
- opacity: opacity || 1,
164
- willChange: "transform, opacity",
165
- }}
166
- >
167
- <motion.div
168
- onHoverStart={() => setHovered(true)}
169
- onHoverEnd={() => setHovered(false)}
170
- animate={{
171
- y: hovered ? -12 : 0,
172
- }}
173
- transition={{ type: "spring", stiffness: 300, damping: 22 }}
174
- style={{
175
- width: "100%",
176
- height: "100%",
177
- borderRadius: "12px",
178
- overflow: "hidden",
179
- position: "relative",
180
- cursor: "pointer",
181
- willChange: "transform",
182
- boxShadow: hovered
183
- ? `0 0 30px rgba(160,120,255,0.8), 0 0 60px rgba(100,80,220,0.6), 0 0 100px rgba(60,40,180,0.4), 0 20px 40px rgba(0,0,0,0.8)`
184
- : `0 10px 20px rgba(0,0,0,0.5)`,
185
- transition: "box-shadow 0.4s ease",
186
- }}
187
- >
188
- <img
189
- src={image.src}
190
- alt={image.label || `Card ${index + 1}`}
191
- loading="lazy"
192
- style={{
193
- width: "100%",
194
- height: "100%",
195
- objectFit: "cover",
196
- display: "block",
197
- borderRadius: "12px",
198
- filter: hovered
199
- ? "brightness(1.2) saturate(1.3) contrast(1.1)"
200
- : `brightness(${0.65 + frontness * 0.3}) saturate(1.05)`,
201
- transition: "filter 0.4s ease",
202
- }}
203
- />
204
-
205
- <div
206
- style={{
207
- position: "absolute",
208
- inset: 0,
209
- background:
210
- "linear-gradient(180deg, rgba(0,0,0,0.05) 0%, rgba(0,0,0,0.55) 100%)",
211
- zIndex: 2,
212
- }}
213
- />
214
-
215
- {image.label && (
216
- <div
217
- style={{
218
- position: "absolute",
219
- bottom: 0,
220
- left: 0,
221
- right: 0,
222
- padding: "20px 16px 16px",
223
- zIndex: 4,
224
- background:
225
- "linear-gradient(0deg, rgba(0,0,0,0.7) 0%, transparent 100%)",
226
- }}
227
- >
228
- <div
229
- style={{
230
- fontFamily: "'SF Pro Display', 'Helvetica Neue', sans-serif",
231
- fontSize: "13px",
232
- fontWeight: 600,
233
- color: "rgba(255,255,255,0.9)",
234
- letterSpacing: "0.08em",
235
- textTransform: "uppercase",
236
- }}
237
- >
238
- {image.label}
239
- </div>
240
- </div>
241
- )}
242
-
243
- <motion.div
244
- animate={hovered ? { opacity: [0, 0.15, 0], x: ["-100%", "200%"] } : { opacity: 0 }}
245
- transition={{ duration: 0.7, ease: "easeInOut" }}
246
- style={{
247
- position: "absolute",
248
- inset: 0,
249
- background:
250
- "linear-gradient(105deg, transparent 40%, rgba(255,255,255,0.4) 50%, transparent 60%)",
251
- zIndex: 5,
252
- }}
253
- />
254
- </motion.div>
255
-
256
- <div
257
- style={{
258
- position: "absolute",
259
- top: "105%",
260
- left: "5%",
261
- right: "5%",
262
- height: "80px",
263
- background: `url(${image.src}) center/cover no-repeat`,
264
- borderRadius: "12px",
265
- transform: "scaleY(-1)",
266
- opacity: 0.12 + frontness * 0.08,
267
- maskImage: "linear-gradient(to bottom, rgba(0,0,0,0.6) 0%, transparent 100%)",
268
- WebkitMaskImage: "linear-gradient(to bottom, rgba(0,0,0,0.6) 0%, transparent 100%)",
269
- filter: "blur(3px)",
270
- pointerEvents: "none",
271
- }}
272
- />
273
- </div>
274
- );
275
- });
276
-
277
- // ─── Main reusable carousel component ────────────────────────────────────────
278
-
279
- function Carousel({
280
- images = [],
281
- defaultRotation = { x: -20, y: 10, z: 20 },
282
- controlled = false,
283
- onRotationChange,
284
- autoRotateSpeed = 0.3,
285
- autoRotateAxes = { x: false, y: true, z: false },
286
- pauseOnDrag = true,
287
- cardWidth = 220,
288
- cardHeight = 300,
289
- gap = 2,
290
- perspective = 2000,
291
- sensitivity = 0.4,
292
- showDragHint = true,
293
- className = "",
294
- style = {},
295
- }) {
296
- const rotationRef = useRef({
297
- x: defaultRotation.x ?? -20,
298
- y: defaultRotation.y ?? 10,
299
- z: defaultRotation.z ?? 20,
300
- });
301
-
302
- const rafRef = useRef(null);
303
- const isDragging = useRef(false);
304
- const lastMousePos = useRef({ x: 0, y: 0 });
305
- const velocityRef = useRef({ x: 0, y: 0, z: 0 });
306
- const [renderRotation, setRenderRotation] = useState({ ...rotationRef.current });
307
- const returnAnimRef = useRef(null);
308
-
309
- const cardCount = images.length;
310
- const angleStep = cardCount > 0 ? 360 / cardCount : 0;
311
- const radius = cardCount > 0 ? ((cardWidth + gap) * cardCount) / (2 * Math.PI) : 0;
312
-
313
- useEffect(() => {
314
- if (!isDragging.current) {
315
- rotationRef.current = {
316
- x: defaultRotation.x ?? -20,
317
- y: defaultRotation.y ?? 10,
318
- z: defaultRotation.z ?? 20,
319
- };
320
- setRenderRotation({ ...rotationRef.current });
321
- }
322
- }, [defaultRotation.x, defaultRotation.y, defaultRotation.z]);
323
-
324
- useEffect(() => {
325
- if (cardCount === 0) return;
326
-
327
- let lastTime = performance.now();
328
-
329
- const tick = (now) => {
330
- const delta = Math.min(now - lastTime, 32);
331
- lastTime = now;
332
-
333
- const isCurrentlyDragging = isDragging.current;
334
-
335
- if (!isCurrentlyDragging) {
336
- velocityRef.current.x *= 0.94;
337
- velocityRef.current.y *= 0.94;
338
- velocityRef.current.z *= 0.94;
339
-
340
- if (Math.abs(velocityRef.current.x) < 0.001) velocityRef.current.x = 0;
341
- if (Math.abs(velocityRef.current.y) < 0.001) velocityRef.current.y = 0;
342
- if (Math.abs(velocityRef.current.z) < 0.001) velocityRef.current.z = 0;
343
-
344
- if (!controlled) {
345
- // Auto-rotate on specified axes
346
- if (autoRotateAxes.x) rotationRef.current.x += autoRotateSpeed * (delta / 16.67);
347
- if (autoRotateAxes.y) rotationRef.current.y += autoRotateSpeed * (delta / 16.67);
348
- if (autoRotateAxes.z) rotationRef.current.z += autoRotateSpeed * (delta / 16.67);
349
-
350
- rotationRef.current.x += velocityRef.current.x;
351
- rotationRef.current.y += velocityRef.current.y;
352
- rotationRef.current.z += velocityRef.current.z;
353
- } else {
354
- rotationRef.current.x += velocityRef.current.x;
355
- rotationRef.current.y += velocityRef.current.y;
356
- rotationRef.current.z += velocityRef.current.z;
357
-
358
- if (Math.abs(velocityRef.current.x) < 0.01 &&
359
- Math.abs(velocityRef.current.y) < 0.01 &&
360
- Math.abs(velocityRef.current.z) < 0.01) {
361
-
362
- const returnSpeed = 0.08;
363
- const targetX = defaultRotation.x ?? -20;
364
- const targetY = defaultRotation.y ?? 10;
365
- const targetZ = defaultRotation.z ?? 20;
366
-
367
- rotationRef.current.x += (targetX - rotationRef.current.x) * returnSpeed;
368
- rotationRef.current.y += (targetY - rotationRef.current.y) * returnSpeed;
369
- rotationRef.current.z += (targetZ - rotationRef.current.z) * returnSpeed;
370
- }
371
- }
372
- }
373
-
374
- setRenderRotation({ ...rotationRef.current });
375
-
376
- if (onRotationChange) {
377
- onRotationChange({ ...rotationRef.current });
378
- }
379
-
380
- rafRef.current = requestAnimationFrame(tick);
381
- };
382
-
383
- rafRef.current = requestAnimationFrame(tick);
384
- return () => {
385
- if (rafRef.current) cancelAnimationFrame(rafRef.current);
386
- if (returnAnimRef.current) cancelAnimationFrame(returnAnimRef.current);
387
- };
388
- }, [controlled, defaultRotation, autoRotateSpeed, autoRotateAxes, pauseOnDrag, onRotationChange, cardCount]);
389
-
390
- const handlePointerDown = useCallback((clientX, clientY) => {
391
- isDragging.current = true;
392
- lastMousePos.current = { x: clientX, y: clientY };
393
- velocityRef.current = { x: 0, y: 0, z: 0 };
394
-
395
- if (returnAnimRef.current) {
396
- cancelAnimationFrame(returnAnimRef.current);
397
- returnAnimRef.current = null;
398
- }
399
- }, []);
400
-
401
- const handlePointerMove = useCallback((clientX, clientY) => {
402
- if (!isDragging.current) return;
403
-
404
- const dx = clientX - lastMousePos.current.x;
405
- const dy = clientY - lastMousePos.current.y;
406
-
407
- // Horizontal drag = Y axis rotation (spin the cylinder)
408
- rotationRef.current.y += dx * sensitivity;
409
-
410
- // Vertical drag = X axis rotation (tilt the cylinder)
411
- rotationRef.current.x -= dy * sensitivity * 0.6;
412
-
413
- // Diagonal drag = Z axis rotation (roll)
414
- if (Math.abs(dx) > 2 && Math.abs(dy) > 2) {
415
- const diagonalComponent = (dx * dy) / Math.sqrt(dx * dx + dy * dy);
416
- rotationRef.current.z += diagonalComponent * sensitivity * 0.3;
417
- }
418
-
419
- velocityRef.current.y = dx * sensitivity * 0.5;
420
- velocityRef.current.x = -dy * sensitivity * 0.3;
421
- velocityRef.current.z = (dx * dy / Math.sqrt(dx * dx + dy * dy || 1)) * sensitivity * 0.2;
422
-
423
- // Clamp values
424
- rotationRef.current.x = Math.max(-75, Math.min(75, rotationRef.current.x));
425
- rotationRef.current.z = Math.max(-45, Math.min(45, rotationRef.current.z));
426
-
427
- lastMousePos.current = { x: clientX, y: clientY };
428
- }, [sensitivity]);
429
-
430
- const handlePointerUp = useCallback(() => {
431
- isDragging.current = false;
432
- }, []);
433
-
434
- const onMouseDown = useCallback((e) => {
435
- e.preventDefault();
436
- handlePointerDown(e.clientX, e.clientY);
437
- }, [handlePointerDown]);
438
-
439
- const onMouseMove = useCallback((e) => {
440
- handlePointerMove(e.clientX, e.clientY);
441
- }, [handlePointerMove]);
442
-
443
- const onMouseUp = useCallback(() => {
444
- handlePointerUp();
445
- }, [handlePointerUp]);
446
-
447
- const onTouchStart = useCallback((e) => {
448
- handlePointerDown(e.touches[0].clientX, e.touches[0].clientY);
449
- }, [handlePointerDown]);
450
-
451
- const onTouchMove = useCallback((e) => {
452
- handlePointerMove(e.touches[0].clientX, e.touches[0].clientY);
453
- }, [handlePointerMove]);
454
-
455
- const onTouchEnd = useCallback(() => {
456
- handlePointerUp();
457
- }, [handlePointerUp]);
458
-
459
- if (cardCount === 0) {
460
- return (
461
- <div className={className} style={{ position: "relative", width: "100%", height: "100%", ...style }}>
462
- <div style={{
463
- position: "absolute",
464
- top: "50%",
465
- left: "50%",
466
- transform: "translate(-50%, -50%)",
467
- color: "rgba(255,255,255,0.5)",
468
- fontSize: "16px"
469
- }}>
470
- No images to display
471
- </div>
472
- </div>
473
- );
474
- }
475
-
476
- // Sort cards by depth for proper rendering order (back to front)
477
- const sortedImages = [...images].map((image, i) => {
478
- const angle = i * angleStep;
479
- const totalAngle = (renderRotation.y + angle) % 360;
480
- const zDepth = Math.cos((totalAngle * Math.PI) / 180);
481
- return { image, index: i, zDepth };
482
- }).sort((a, b) => a.zDepth - b.zDepth);
483
-
484
- return (
485
- <div
486
- className={className}
487
- style={{
488
- position: "relative",
489
- width: "100%",
490
- height: "100%",
491
- perspective: `${perspective}px`,
492
- perspectiveOrigin: "50% 50%",
493
- cursor: isDragging.current ? "grabbing" : "grab",
494
- userSelect: "none",
495
- touchAction: "none",
496
- ...style,
497
- }}
498
- onMouseDown={onMouseDown}
499
- onMouseMove={onMouseMove}
500
- onMouseUp={onMouseUp}
501
- onMouseLeave={onMouseUp}
502
- onTouchStart={onTouchStart}
503
- onTouchMove={onTouchMove}
504
- onTouchEnd={onTouchEnd}
505
- >
506
- {/* OUTER CONTAINER: Only tilts the view (X and Z rotation) */}
507
- <div
508
- style={{
509
- position: "absolute",
510
- top: "50%",
511
- left: "50%",
512
- transformStyle: "preserve-3d",
513
- transform: `translate(-50%, -50%) rotateX(${renderRotation.x}deg) rotateZ(${renderRotation.z}deg)`,
514
- willChange: "transform",
515
- }}
516
- >
517
- {/* INNER CONTAINER: Only spins the cards around Y axis */}
518
- <div
519
- style={{
520
- position: "absolute",
521
- top: "50%",
522
- left: "50%",
523
- transformStyle: "preserve-3d",
524
- transform: `translate(-50%, -50%) rotateY(${renderRotation.y}deg)`,
525
- willChange: "transform",
526
- }}
527
- >
528
- {sortedImages.map(({ image, index, zDepth }) => (
529
- <CarouselCard
530
- key={index}
531
- image={image}
532
- index={index}
533
- totalAngle={renderRotation.y}
534
- opacity={0.3 + (zDepth + 1) * 0.35}
535
- angleStep={angleStep}
536
- radius={radius}
537
- cardWidth={cardWidth}
538
- cardHeight={cardHeight}
539
- />
540
- ))}
541
- </div>
542
- </div>
543
-
544
- {showDragHint && (
545
- <motion.div
546
- initial={{ opacity: 0 }}
547
- animate={{ opacity: 1 }}
548
- transition={{ delay: 2.5, duration: 1 }}
549
- style={{
550
- position: "absolute",
551
- bottom: "28px",
552
- left: "50%",
553
- transform: "translateX(-50%)",
554
- zIndex: 10,
555
- color: "rgba(255,255,255,0.25)",
556
- fontSize: "11px",
557
- letterSpacing: "0.2em",
558
- textTransform: "uppercase",
559
- textAlign: "center",
560
- pointerEvents: "none",
561
- }}
562
- >
563
- drag to rotate • auto-rotating
564
- </motion.div>
565
- )}
566
- </div>
567
- );
568
- }
569
-
570
- // ─── Full page component with backgrounds ────────────────────────────────────
571
-
572
- function CarouselWithBackground({
573
- images,
574
- defaultRotation,
575
- controlled,
576
- onRotationChange,
577
- ...carouselProps
578
- }) {
579
- const containerRef = useRef(null);
580
- const [mouse, setMouse] = useState({ x: 0, y: 0 });
581
-
582
- useEffect(() => {
583
- const onResize = () => {
584
- setMouse(prev => ({ ...prev }));
585
- };
586
- window.addEventListener("resize", onResize);
587
- return () => window.removeEventListener("resize", onResize);
588
- }, []);
589
-
590
- const onMouseMove = useCallback((e) => {
591
- setMouse({ x: e.clientX, y: e.clientY });
592
- }, []);
593
-
594
- return (
595
- <div
596
- ref={containerRef}
597
- onMouseMove={onMouseMove}
598
- style={{
599
- position: "relative",
600
- width: "100vw",
601
- height: "100vh",
602
- background: "#000",
603
- overflow: "hidden",
604
- fontFamily: "'SF Pro Display', 'Helvetica Neue', sans-serif",
605
- }}
606
- >
607
- <LightBlobs mouseX={mouse.x} mouseY={mouse.y} />
608
- <Particles />
609
-
610
- <div
611
- style={{
612
- position: "absolute",
613
- inset: 0,
614
- background:
615
- "radial-gradient(ellipse 60% 40% at 50% 65%, rgba(80,50,160,0.18) 0%, transparent 70%)",
616
- pointerEvents: "none",
617
- zIndex: 1,
618
- }}
619
- />
620
-
621
- <div
622
- style={{
623
- position: "absolute",
624
- bottom: 0,
625
- left: 0,
626
- right: 0,
627
- height: "35%",
628
- background:
629
- "linear-gradient(to top, rgba(20,10,40,0.5) 0%, transparent 100%)",
630
- pointerEvents: "none",
631
- zIndex: 2,
632
- }}
633
- />
634
-
635
- <div style={{ position: "absolute", inset: 0, zIndex: 5 }}>
636
- <Carousel
637
- images={images}
638
- defaultRotation={defaultRotation}
639
- controlled={controlled}
640
- onRotationChange={onRotationChange}
641
- {...carouselProps}
642
- />
643
- </div>
644
- </div>
645
- );
646
- }
647
-
648
- export { Carousel, CarouselWithBackground };
649
- export default Carousel;
package/src/index.js DELETED
@@ -1,17 +0,0 @@
1
- import React from 'react';
2
- import ReactDOM from 'react-dom/client';
3
- import './index.css';
4
- import App from './App';
5
- import reportWebVitals from './reportWebVitals';
6
-
7
- const root = ReactDOM.createRoot(document.getElementById('root'));
8
- root.render(
9
- <React.StrictMode>
10
- <App />
11
- </React.StrictMode>
12
- );
13
-
14
- // If you want to start measuring performance in your app, pass a function
15
- // to log results (for example: reportWebVitals(console.log))
16
- // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17
- reportWebVitals();
@@ -1,13 +0,0 @@
1
- const reportWebVitals = onPerfEntry => {
2
- if (onPerfEntry && onPerfEntry instanceof Function) {
3
- import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4
- getCLS(onPerfEntry);
5
- getFID(onPerfEntry);
6
- getFCP(onPerfEntry);
7
- getLCP(onPerfEntry);
8
- getTTFB(onPerfEntry);
9
- });
10
- }
11
- };
12
-
13
- export default reportWebVitals;
package/src/setupTests.js DELETED
@@ -1,5 +0,0 @@
1
- // jest-dom adds custom jest matchers for asserting on DOM nodes.
2
- // allows you to do things like:
3
- // expect(element).toHaveTextContent(/react/i)
4
- // learn more: https://github.com/testing-library/jest-dom
5
- import '@testing-library/jest-dom';
File without changes
File without changes
File without changes